﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Drawing.Drawing2D;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using DriveBeamEditor.Tools;

namespace DriveBeamEditor.Controls.General
{
    public partial class DualContainer : SplitContainer
    {
        #region Constants

        public enum SplitterSizes
        {
            Tiny = 5,
            Small = 10,
            Medium = 20,
            Large = 30,
            XtraLarge = 40
        }

        #endregion

        #region Fields

        private static readonly Color HotColor = Imaging.Blend(SystemColors.Highlight, SystemColors.Window, 70);

        private Color _backColor2 = Color.FromArgb(255, 108, 0);
        private Color _backColorHover = Color.FromArgb(255, 138, 0);
        private Color _arrowColor = Color.FromKnownColor(KnownColor.Gray);
        private Color _arrowHoverColor = Color.FromKnownColor(KnownColor.PaleGoldenrod);

        private SplitterSizes _splitterSize = SplitterSizes.Medium;
        private int _splitterResetDistance = -1;

        private bool _isMouseOver = false;
        private bool _isOverArrow1 = false;
        private bool _isOverArrow2 = false;
        private bool _isOverCenter = false;

        private Cursor _oldCursor = Cursors.Default;

        #endregion

        #region Properties

        public Color BackColor2
        {
            get { return _backColor2; }
            set
            {
                if (value == _backColor2) return;
                _backColor2 = value;
                Invalidate();
            }
        }

        public Color BackColorHover
        {
            get { return _backColorHover; }
            set
            {
                if (value == _backColorHover) return;
                _backColorHover = value;
                Invalidate();
            }
        }

        public Color ArrowColor
        {
            get { return _arrowColor; }
            set
            {
                if (value == _arrowColor) return;
                _arrowColor = value;
                Invalidate();
            }
        }

        public Color ArrowHoverColor
        {
            get { return _arrowHoverColor; }
            set
            {
                if (value == _arrowHoverColor) return;
                _arrowHoverColor = value;
                Invalidate();
            }
        }

        public SplitterSizes SplitterSize
        {
            get { return _splitterSize; }
            set
            {
                if (value == _splitterSize) return;
                _splitterSize = value;
                Invalidate();
            }
        }

        public int SplitterResetDistance
        {
            get { return _splitterResetDistance; }
            set
            {
                if (value == _splitterResetDistance) return;
                _splitterResetDistance = value;
                Invalidate();
            }
        }

        #endregion

        #region CTORs

        public DualContainer()
        {
            InitializeComponent();
            SetStyle(
                ControlStyles.AllPaintingInWmPaint |
                ControlStyles.DoubleBuffer |
                ControlStyles.Opaque |
                ControlStyles.OptimizedDoubleBuffer |
                ControlStyles.ResizeRedraw |
                ControlStyles.UserPaint,
                true
            );
        }

        #endregion

        #region Overrides

        protected override void OnPaint(PaintEventArgs e)
        {
            /* Update splitter width if needed */
            SplitterWidth = (int)SplitterSize;

            /* Variables */
            var g = e.Graphics;
            var cr = ClientRectangle;
            var o = Orientation;
            var w = (float)ClientSize.Width;
            var h = (float)ClientSize.Height;
            var sd = SplitterDistance;
            var sw = SplitterWidth;
            var a = sw / 4f;
            var aa = a + 2f;
            var cur = PointToClient(Cursor.Position);

            e.Graphics.CompositingQuality = CompositingQuality.HighQuality;
            e.Graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;

            /* Determine splitter rectangle */
            var sr = o == Orientation.Horizontal ? new RectangleF(0f, sd, w, sw) : new RectangleF(sd, 0f, sw, h);

            /* Determine colors */
            _isMouseOver = sr.Contains(cur);
            var c1 = BackColor;
            var c2 = _isMouseOver ? BackColorHover : BackColor2;

            /* Draw splitter background */
            g.FillRectangle(new SolidBrush(c1), cr);
            if (o == Orientation.Horizontal)
            {
                var bh = new LinearGradientBrush(new RectangleF(0f, sd, w, sw), c1, c2, LinearGradientMode.Vertical);
                var bh2 = new LinearGradientBrush(new RectangleF(0f, sd + sw * 0.5f, w, sw * 0.5f), Color.FromArgb(50, c2), c2, LinearGradientMode.Vertical);
                g.FillRectangle(bh, 0f, sd, w, sw);
                g.FillRectangle(bh2, 0f, sd + sw * 0.5f + 1f, w, sw * 0.5f);
            }
            else
            {
                var bv = new LinearGradientBrush(new RectangleF(sd, 0f, sw, h), c1, c2, LinearGradientMode.Horizontal);
                var bv2 = new LinearGradientBrush(new RectangleF(sd + sw * 0.5f, 0f, sw * 0.5f, h), Color.FromArgb(50, c2), c2, LinearGradientMode.Horizontal);
                g.FillRectangle(bv, sd, 0f, sw, h);
                g.FillRectangle(bv2, sd + sw * 0.5f + 1f, 0f, sw * 0.5f, h);
            }

            /* Draw toggling arrows */
            var arrow1 = new PointF[3];
            var arrow1s = new PointF[3];
            var arrow2 = new PointF[3];
            var arrow2s = new PointF[3];
            var center = PointF.Empty;
            if (o == Orientation.Horizontal)
            {
                center = new PointF(w - w / 4f, sd + sw / 2f);
                arrow1[0] = PointF.Add(center, new SizeF(-a, a));
                arrow1[1] = PointF.Add(center, new SizeF(0f, -a));
                arrow1[2] = PointF.Add(center, new SizeF(a, a));
                arrow1s = new[]
                {
                    new PointF(arrow1[0].X - 2f, arrow1[0].Y + 2f),
                    new PointF(arrow1[1].X, arrow1[1].Y - 3f),
                    new PointF(arrow1[2].X + 2f, arrow1[2].Y + 2f)
                };
                _isOverArrow1 = new RectangleF(center.X - aa, center.Y - aa, aa * 2, aa * 2).Contains(cur);

                center = new PointF(w / 4f, sd + sw / 2f);
                arrow2[0] = PointF.Add(center, new SizeF(-a, -a));
                arrow2[1] = PointF.Add(center, new SizeF(a, -a));
                arrow2[2] = PointF.Add(center, new SizeF(0f, a));
                arrow2s = new[]
                {
                    new PointF(arrow2[0].X - 2f, arrow2[0].Y - 2f),
                    new PointF(arrow2[1].X + 2f, arrow2[1].Y - 2f),
                    new PointF(arrow2[2].X, arrow2[2].Y + 3f)
                };
                _isOverArrow2 = new RectangleF(center.X - aa, center.Y - aa, aa * 2, aa * 2).Contains(cur);
            }
            else
            {
                center = new PointF(sd + sw / 2f, h / 4f);
                arrow1[0] = PointF.Add(center, new SizeF(-a, 0f));
                arrow1[1] = PointF.Add(center, new SizeF(a, -a));
                arrow1[2] = PointF.Add(center, new SizeF(a, a));
                arrow1s = new[]
                {
                    new PointF(arrow1[0].X - 3f, arrow1[0].Y),
                    new PointF(arrow1[1].X + 2f, arrow1[1].Y - 2f),
                    new PointF(arrow1[2].X + 2f, arrow1[2].Y + 2f)
                };
                _isOverArrow1 = new RectangleF(center.X - aa, center.Y - aa, aa * 2, aa * 2).Contains(cur);

                center = new PointF(sd + sw / 2f, h - h / 4f);
                arrow2[0] = PointF.Add(center, new SizeF(-a, -a));
                arrow2[1] = PointF.Add(center, new SizeF(a, 0f));
                arrow2[2] = PointF.Add(center, new SizeF(-a, a));
                arrow2s = new[]
                {
                    new PointF(arrow2[0].X - 2f, arrow2[0].Y - 2f),
                    new PointF(arrow2[1].X + 3f, arrow2[1].Y),
                    new PointF(arrow2[2].X - 2f, arrow2[2].Y + 2f)
                };
                _isOverArrow2 = new RectangleF(center.X - aa, center.Y - aa, aa * 2, aa * 2).Contains(cur);
            }
            g.FillPolygon(new SolidBrush(_isOverArrow1 ? BackColor2 : BackColor), arrow1s);
            g.FillPolygon(new SolidBrush(_isOverArrow1 ? _arrowHoverColor : _arrowColor), arrow1);
            g.FillPolygon(new SolidBrush(_isOverArrow2 ? BackColor2 : BackColor), arrow2s);
            g.FillPolygon(new SolidBrush(_isOverArrow2 ? _arrowHoverColor : _arrowColor), arrow2);

            /* Draw center button */
            var pol = new PointF[12];
            if (o == Orientation.Horizontal)
            {
                center = new PointF(w / 2f, sd + sw / 2f);
                pol[0] = PointF.Add(center, new SizeF(-a, -a));
                pol[1] = PointF.Add(center, new SizeF(a, -a));
                pol[2] = PointF.Add(center, new SizeF(a / 2f, -1f));
                pol[3] = PointF.Add(center, new SizeF(a, -1f));
                pol[4] = PointF.Add(center, new SizeF(a, 1f));
                pol[5] = PointF.Add(center, new SizeF(a / 2f, 1f));
                pol[6] = PointF.Add(center, new SizeF(a, a));
                pol[7] = PointF.Add(center, new SizeF(-a, a));
                pol[8] = PointF.Add(center, new SizeF(-a / 2f, 1f));
                pol[9] = PointF.Add(center, new SizeF(-a, 1f));
                pol[10] = PointF.Add(center, new SizeF(-a, -1f));
                pol[11] = PointF.Add(center, new SizeF(-a / 2f, -1f));
                _isOverCenter = new RectangleF(center.X - aa, center.Y - aa, aa * 2, aa * 2).Contains(cur);
            }
            else
            {
                center = new PointF(sd + sw / 2f, h / 2f);
                pol[0] = PointF.Add(center, new SizeF(-a, a));
                pol[1] = PointF.Add(center, new SizeF(-a, -a));
                pol[2] = PointF.Add(center, new SizeF(-1f, -a / 2f));
                pol[3] = PointF.Add(center, new SizeF(-1f, -a));
                pol[4] = PointF.Add(center, new SizeF(1f, -a));
                pol[5] = PointF.Add(center, new SizeF(1f, -a / 2f));
                pol[6] = PointF.Add(center, new SizeF(a, -a));
                pol[7] = PointF.Add(center, new SizeF(a, a));
                pol[8] = PointF.Add(center, new SizeF(1f, a / 2f));
                pol[9] = PointF.Add(center, new SizeF(1f, a));
                pol[10] = PointF.Add(center, new SizeF(-1f, a));
                pol[11] = PointF.Add(center, new SizeF(-1f, a / 2f));
                _isOverCenter = new RectangleF(center.X - aa, center.Y - aa, aa * 2, aa * 2).Contains(cur);
            }
            g.FillPolygon(new SolidBrush(_isOverCenter ? _arrowHoverColor : _arrowColor), pol);

            /* Update cursor if needed */
            if (_isOverArrow1 || _isOverArrow2 || _isOverCenter)
                Cursor = Cursors.Hand;
            else if (_isMouseOver)
                Cursor = Orientation == Orientation.Horizontal ? Cursors.HSplit : Cursors.VSplit;
            else
                Cursor = _oldCursor;

            base.OnPaint(e);
        }

        #endregion

        #region Events

        private void DualContainer_SplitterMoved(object sender, SplitterEventArgs e)
        {
            Invalidate();
        }

        private void DualContainer_SplitterMoving(object sender, SplitterCancelEventArgs e)
        {
        }

        private void DualContainer_MouseMove(object sender, MouseEventArgs e)
        {
            Invalidate();
        }

        private void DualContainer_MouseEnter(object sender, EventArgs e)
        {
            _oldCursor = Cursor;
            _isMouseOver = true;
            Invalidate();
        }

        private void DualContainer_MouseLeave(object sender, EventArgs e)
        {
            _isMouseOver = false;
            Invalidate();
        }

        private void DualContainer_MouseDown(object sender, MouseEventArgs e)
        {
            Invalidate();

            try
            {
                if (_isOverArrow1)
                {
                    SplitterDistance = Panel1MinSize;
                }
                else if (_isOverArrow2)
                {
                    switch (Orientation)
                    {
                        case Orientation.Horizontal:
                            SplitterDistance = ClientSize.Height - Panel2MinSize;
                            break;
                        case Orientation.Vertical:
                            SplitterDistance = ClientSize.Width - Panel2MinSize;
                            break;
                    }
                }
                else if (_isOverCenter)
                {
                    Pop();
                }
            }
            catch (ArgumentOutOfRangeException) { /* Negative splitter distance */ }
            catch (InvalidOperationException) { /* Invalid splitter distance */ }

            Invalidate();
        }

        private void DualContainer_Panel1_MouseEnter(object sender, EventArgs e)
        {
            _isMouseOver = false;
            Invalidate();
        }

        private void DualContainer_Panel2_MouseEnter(object sender, EventArgs e)
        {
            _isMouseOver = false;
            Invalidate();
        }

        #endregion

        #region Methods

        public void Pop()
        {
            if (SplitterResetDistance < 0)
            {
                switch (Orientation)
                {
                    case Orientation.Horizontal:
                        SplitterDistance = ClientSize.Height / 2 - SplitterWidth / 2;
                        break;
                    case Orientation.Vertical:
                        SplitterDistance = ClientSize.Width / 2 - SplitterWidth / 2;
                        break;
                }
            }
            else if (FixedPanel == FixedPanel.Panel1 || FixedPanel == FixedPanel.None)
            {
                SplitterDistance = SplitterResetDistance;
            }
            else
            {
                SplitterDistance =
                    Orientation == Orientation.Horizontal
                        ? ClientSize.Height - SplitterResetDistance
                        : ClientSize.Width - SplitterResetDistance;
            }
            Invalidate();
        }

        public void Pull()
        {
            if (FixedPanel == FixedPanel.Panel1 || FixedPanel == FixedPanel.None)
            {
                SplitterDistance = Panel1MinSize;
            }
            else
            {
                switch (Orientation)
                {
                    case Orientation.Horizontal:
                        SplitterDistance = ClientSize.Height - Panel2MinSize;
                        break;
                    case Orientation.Vertical:
                        SplitterDistance = ClientSize.Width - Panel2MinSize;
                        break;
                }
            }
        }

        #endregion
    }
}
